//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-11-07     ZhangYihua   first version
//
// Description  : 
//################################################################################

module dly_mem #(
parameter           DW                      = 24,
parameter           DEPTH                   = 128,
parameter           MEM_STYLE               = "TINY_MEM",   // "TPRAM"
parameter           EARLY_DATA              = "XXXX",       // "ZERO"
parameter           ECC_MODE                = "NONE",       // no ECC
//parameter           ECC_MODE                = "SEC_ONLY",   // Single Error Correction only, not support Double Error Detection
//parameter           ECC_MODE                = "SEC_DED",    // Single Error-bit Correction and Double Error-bit Detecttion

// the following parameters are calculated automatically
parameter           MEM_RL                  = ((MEM_STYLE=="TINY_MEM") ? 1 : 2),
parameter           DLY_BW                  = $clog2(DEPTH)
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input               [DW-1:0]                id,     // data before delay
output              [DW-1:0]                od,     // actual delay is (cfg_dly+2)*cke + (mem_rd_latency-1)

input               [DLY_BW-1:0]            cfg_dly // 0<=cfg_dly<=DEPTH-2
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          ADDR_BW                 = $clog2(DEPTH);
localparam [ADDR_BW-1:0]    DEPTH_V         = DEPTH;        // if DEPTH==2^ADDR_BW, DEPTH_V={ADDR_BW{1'b0}}
localparam [ADDR_BW-1:0]    DEPTH_DEC       = DEPTH-1;

reg                                         empty;
reg                 [ADDR_BW-1:0]           waddr;
wire                [ADDR_BW-1:0]           raddr_c;
reg                 [ADDR_BW-1:0]           raddr;
wire                                        mem_ren;
wire                                        mem_rvld;

//################################################################################
// main
//################################################################################

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        waddr <=`U_DLY {ADDR_BW{1'b0}};
    end else if (cke==1'b1) begin
        if (waddr==DEPTH_DEC)
            waddr <=`U_DLY {ADDR_BW{1'b0}};
        else
            waddr <=`U_DLY waddr + 1'd1;
    end else
        ;
end

// cfg_dly = N  (0<=N<=DEPTH-2)
// id data          |  Dx | ...........  
// waddr            |  0  |  1  |  .........     |  N  | N+1 | N+2 |  ........
// raddr                           .........           |  0  |  1  |  ........
// FIFO logic delay    <---------------  N+1  ------------>
// mem_rd_latency                                         <----->
// od data                                                   |  Dx |  ........

assign raddr_c = (waddr>=cfg_dly) ? (waddr - cfg_dly) :
                                    (waddr - cfg_dly + DEPTH_V);
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        raddr <=`U_DLY {ADDR_BW{1'b0}};
    end else if (cke==1'b1) begin
        raddr <=`U_DLY raddr_c;
    end else
        ;
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        empty <=`U_DLY 1'b1;
    end else if (cke==1'b1) begin
        if (raddr==DEPTH_DEC)
            empty <=`U_DLY 1'b0;
        else
            ;
    end
end

assign mem_ren = (~empty) & cke;

generate if (MEM_STYLE=="TINY_MEM") begin:G_TINY_MEM
    tiny_mem #(
            .DEPTH                          (DEPTH                          ),
            .DATA_BW                        (DW                             ),
            .OUT_REG_EN                     (1'b1                           ) 	// read data is REG, total depth is DEPTH+1
    ) u_mem ( 
            .rst_wr_n                       (rst_n                          ),
            .clk_wr                         (clk                            ),
            .mem_wen                        (cke                            ),
            .mem_waddr                      (waddr                          ),
            .mem_wdata                      (id                             ),

            .rst_rd_n                       (rst_n                          ),
            .clk_rd                         (clk                            ),
            .mem_ren                        (mem_ren                        ),
            .mem_raddr                      (raddr                          ),
            .mem_rvld                       (mem_rvld                       ),
            .mem_rdata                      (od                             )
    );
end else if (MEM_STYLE=="TPRAM") begin:G_TPRAM
    wire                [DW-1:0]                od_c;

    tpram_wrap #(
            .DEPTH                          (DEPTH                          ),
            .DATA_BW                        (DW                             ),
            .RDPL_NUM                       (MEM_RL                         ),
            .USER_DEF_TAG                   ("NONE"                         ),
            .ECC_MODE                       (ECC_MODE                       )
    ) u_mem ( 
            .rst_wr_n                       (rst_n                          ),
            .clk_wr                         (clk                            ),
            .mem_wen                        (cke                            ),
            .mem_waddr                      (waddr                          ),
            .mem_wdata                      (id                             ),
            .cfg_frc_sbe                    (1'b0                           ),

            .rst_rd_n                       (rst_n                          ),
            .clk_rd                         (clk                            ),
            .mem_ren                        (mem_ren                        ),
            .mem_rdpl_ce                    ({MEM_RL{1'b1}}                 ),
            .mem_raddr                      (raddr                          ),
            .mem_rvld                       (mem_rvld                       ),
            .mem_rdata                      (od_c                           ),
            .alm_ecc_err                    (                               ),	// support at ECC_MODE
            .alm_ecc_dbe                    (                               )	// support at ECC_MODE
    );

    if (EARLY_DATA=="ZERO") begin:G_X
        reg                 [MEM_RL-1:0]            empty_dly;

        always@(posedge clk or negedge rst_n) begin
            if (rst_n==1'b0) begin
                empty_dly[0] <=`U_DLY 1'b1;
            end else if (cke==1'b1) begin
                empty_dly[0] <=`U_DLY empty;
            end else
                ;
        end

        if (MEM_RL>=2) begin:G_MORE
            always@(posedge clk or negedge rst_n) begin
                if (rst_n==1'b0) begin
                    empty_dly[1+:MEM_RL-1] <=`U_DLY {MEM_RL-1{1'b1}};
                end else begin
                    empty_dly[1+:MEM_RL-1] <=`U_DLY empty_dly[0+:MEM_RL-1];
                end
            end
        end

        assign od = (empty_dly[MEM_RL-1]==1'b0) ? od_c : {DW{1'b0}};

    end else begin:G_0

        assign od = od_c;

    end

end else begin:G_NULL
`ifdef PARAMETER_NULL_CHECK  
    // if 'MEM_STYLE' wrong, force compiler to error for attention.
    mem_null u_null();
`endif
end endgenerate


//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
